package se.devoteam.nexus.ldap;

import javax.naming.NamingException;
import javax.naming.ldap.LdapContext;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.LogEnabled;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
import org.jsecurity.authc.AuthenticationInfo;
import org.jsecurity.authc.AuthenticationToken;
import org.jsecurity.authc.SimpleAuthenticationInfo;
import org.jsecurity.authc.UsernamePasswordToken;
import org.jsecurity.authz.AuthorizationInfo;
import org.jsecurity.cache.ehcache.EhCacheManager;
import org.jsecurity.realm.Realm;
import org.jsecurity.realm.ldap.AbstractLdapRealm;
import org.jsecurity.realm.ldap.DefaultLdapContextFactory;
import org.jsecurity.realm.ldap.LdapContextFactory;
import org.jsecurity.realm.ldap.LdapUtils;
import org.jsecurity.subject.PrincipalCollection;

/**
 * LDAP authenticating realm for Sonatype Nexus.
 * 
 * @author Anders Hammar
 */
@Component(role = Realm.class, hint = "LdapAuthenticatingRealm")
public class LdapAuthRealm extends AbstractLdapRealm implements Initializable, Disposable, LogEnabled {

	private Logger logger;
	private String principalPrefix;

    @Requirement
	private LdapConfigurationManager configuration;

	@Override
	public String getName() {
		return LdapAuthRealm.class.getName();
	}

	public String getPrincipalPrefix() {
		return this.principalPrefix;
	}

	public void setPrincipalPrefix(String principalPrefix) {
		this.principalPrefix = principalPrefix;
	}

	protected Logger getLogger() {
		return logger;
	}

	public void initialize() throws InitializationException {
		getLogger().info("Initializing LdapAuthenticatingRealm");
		String ldapUrl = this.configuration.getUrl();
		String ldapPrincipalPrefix = this.configuration.getPrincipalPrefix();
		String ldapPrincipalSuffix = this.configuration.getPrincipalSuffix();
		if (getLogger().isDebugEnabled()) {
			getLogger().debug("LDAP connect url: " + ldapUrl);
			getLogger().debug("LDAP principal prefix: " + ldapPrincipalPrefix);
			getLogger().debug("LDAP principal suffix: " + ldapPrincipalSuffix);
		}
		super.setUrl(ldapUrl);
		setPrincipalPrefix(ldapPrincipalPrefix);
		super.setPrincipalSuffix(ldapPrincipalSuffix);
		super.setSearchBase(""); // not being used, but can't be null

		// As we don't use a cache manager, no DefaultLdapContextFactory is created.
		// This is probably a bug and reported in JSEC-131.
        DefaultLdapContextFactory ldapContextFactory = new DefaultLdapContextFactory();
        ldapContextFactory.setPrincipalSuffix(ldapPrincipalSuffix);
        ldapContextFactory.setSearchBase(""); // not being used, but can't be null
        ldapContextFactory.setUrl(ldapUrl);
        super.setLdapContextFactory(ldapContextFactory);

		super.init();
	}

	public void dispose() {
		getLogger().info("Disposing LdapAuthenticatingRealm");
	}

	public void enableLogging(Logger logger) {
		this.logger = logger;
	}

	/**
	 * Builds an {@link AuthenticationInfo} object by querying the LDAP context
	 * for the specified username. This method binds to the LDAP server using
	 * the provided username and password - which, if successful, indicates that
	 * the password is correct.
	 * 
	 * @param token
	 *            the authentication token provided by the user
	 * @param ldapContextFactory
	 *            the factory used to build connections to the LDAP server
	 * @return an AuthenticationInfo instance containing information retrieved
	 *         from LDAP
	 * @throws NamingException
	 *             if any LDAP errors occur during the search
	 */
	@Override
	protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
			LdapContextFactory ldapContextFactory) throws NamingException {
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;

		LdapContext ctx = null;
		try {
			String username = upToken.getUsername();
			if (getLogger().isDebugEnabled()) {
				getLogger().debug("Authenticating user '" + username + "' through LDAP");
			}
			String tmp = getPrincipalPrefix();
			if (username != null && tmp != null) {
				username = tmp + username;
			}
			// Binds using the username and password provided by the user.
			ctx = ldapContextFactory.getLdapContext(username, String.valueOf(upToken.getPassword()));
		} finally {
			LdapUtils.closeContext(ctx);
		}

		getLogger().debug("User authenticated through LDAP");
		return buildAuthenticationInfo(upToken.getUsername(), upToken.getPassword());
	}

	protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) {
		return new SimpleAuthenticationInfo(username, password, getName());
	}

	/**
	 * No authorization taking place (always returns <code>null</code>).
	 * 
	 * @return <code>null</code>
	 */
	protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals,
			LdapContextFactory ldapContextFactory) throws NamingException {
		return null;
	}
}
